package com.fyyg.common.db;


import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

/**
 * Created by fy on 2016/7/27.
 */
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class DynamicTableIntecepter implements Interceptor {
    private static final Log log = LogFactory.getLog(DynamicTableIntecepter.class);

    private static final String tag = DynamicTableIntecepter.class.getName();
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REF_FACTORY = new DefaultReflectorFactory();

    private String[] dynamicTables = null;
    private Map<String,String> defaultTableMap = null;
    private Map<String,String> dynamicTableShardKey = null;
    /**
     * 拦截后要执行的方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation .getTarget();

        MetaObject metaStatementHandler = MetaObject.forObject(
                                                                statementHandler,
                                                                DEFAULT_OBJECT_FACTORY,
                                                                DEFAULT_OBJECT_WRAPPER_FACTORY,
                                                                DEFAULT_REF_FACTORY);

        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        String sql = boundSql.getSql();
        Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        String virtualTable = null;
        if (sql!=null && !sql.equals("") && (virtualTable = Dynamic(sql)) != null) {

            String shardColumn = dynamicTableShardKey.get(virtualTable);
            if(defaultTableMap.containsKey(virtualTable)){
                if(defaultTableMap.get(virtualTable).equals(virtualTable))
                     return invocation.proceed();
                else
                    throw new RuntimeException("不支持。。。。。");
            }

            String dynamicValue = null;

            List<ParameterMapping> list = boundSql.getParameterMappings();
            for (ParameterMapping parameterMapping : list) {
                if(parameterMapping.getProperty().equals(shardColumn)){
                     parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
                    if(parameterObject == null){
                        throw new RuntimeException("请设置正确的分表参数。。。。");
                    }
                    if(parameterObject instanceof MapperMethod.ParamMap){
                        dynamicValue = ""+((MapperMethod.ParamMap) parameterObject).get(shardColumn);
                    }else if(parameterObject instanceof String){
                        dynamicValue = String.valueOf(parameterObject);
                    }else if(parameterObject instanceof Integer){
                        dynamicValue = String.valueOf(parameterObject);
                    }else{
                        Field[] fields = parameterObject.getClass().getDeclaredFields();
                        for (int j = 0; j < fields.length; j++) {
                            fields[j].setAccessible(true);
                            if(fields[j].getName().equals(shardColumn)) {
                                dynamicValue = fields[j].get(parameterObject) + "";
                                break;
                            }
                        }
                    }
                    if(dynamicValue == null || dynamicValue.equals("")){
                        throw new RuntimeException("请设置正确的分表参数。。。。");
                    }
                    String dynamicTable = virtualTable +"_"+dynamicValue;
                    sql = sql.replace(virtualTable,dynamicTable);
                    ReflectUtil.setFieldValue(boundSql, "sql", sql);
                    break;
                }
            }

        }
        // 传递给下一个拦截器处理
        return invocation.proceed();
    }

    private String Dynamic(String sql){
        for (int i = 0; i < dynamicTables.length; i++) {
            if(sql.contains(dynamicTables[i]))
                return dynamicTables[i];
        }
        return null;
    }

    /**
     * 注册插件
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 获取配置属性
     */
    @Override
    public void setProperties(Properties properties) {
        dynamicTables = properties.getProperty("dynamicTable").split(",");
        String[] defaultTableStr = properties.getProperty("defaultTable").split(",");
        defaultTableMap = new HashMap<>();
        for (int i = 0; i < defaultTableStr.length; i++) {
            String[] defTableEntry = defaultTableStr[i].split(":");
            defaultTableMap.put(defTableEntry[0],defTableEntry[1]);
        }
        dynamicTableShardKey = new HashMap<>();
        String[] defaultTableShardStr = properties.getProperty("shardkey").split(",");
        for (int i = 0; i < defaultTableShardStr.length; i++) {
            String[] defTableEntry = defaultTableShardStr[i].split(":");
            dynamicTableShardKey.put(defTableEntry[0],defTableEntry[1]);
        }
    }

    /**
     * 反射工具类
     * @author hang.ouyang
     *
     * @date 2016年3月16日 下午5:48:59
     */
    private static class ReflectUtil {

        /**
         * 利用反射获取指定对象的指定属性
         * @param obj  目标对象
         * @param fieldName 目标属性
         * @return 目标属性的值
         * @author hang.ouyang
         * @date 2016年3月16日 下午5:48:40
         */
        public static Object getFieldValue(Object obj, String fieldName) {
            Field field = getField(obj, fieldName);
            if (field == null) {
                return null;
            }

            Object result = null;
            try {
                field.setAccessible(true);
                result = field.get(obj);
            } catch (IllegalArgumentException e) {
                log.error("illegal argument", e);
            } catch (IllegalAccessException e) {
                log.error("illegal access", e);
            }

            return result;
        }

        /**
         * 利用反射获取指定对象里面的指定属性
         * @param obj 目标对象
         * @param fieldName  目标属性
         * @return 目标字段
         * @author hang.ouyang
         * @date 2016年3月16日 下午5:10:49
         */
        private static Field getField(Object obj, String fieldName) {
            Field field = null;
            for (Class<?> clazz = obj.getClass(); clazz != Object.class;
                 clazz = clazz.getSuperclass()) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    // 这里不用做处理，子类没有该字段可能对应的父类有，都没有就返回null。
                }
            }
            return field;
        }

        /**
         * 利用反射设置指定对象的指定属性为指定的值
         * @param obj 目标对象
         * @param fieldName 目标属性
         * @param fieldValue 目标值
         * @author hang.ouyang
         * @date 2016年3月16日 下午5:10:07
         */
        public static void setFieldValue(Object obj, String fieldName,
                                         String fieldValue) {
            Field field = getField(obj, fieldName);
            if (field == null) {
                return;
            }
            try {
                field.setAccessible(true);
                field.set(obj, fieldValue);
            } catch (IllegalArgumentException e) {
                log.error("illegal argument", e);
            } catch (IllegalAccessException e) {
                log.error("illegal access", e);
            }
        }
    }

}
